package com.fanvil.videoconference;

import android.content.Context;
import android.graphics.SurfaceTexture;
import android.graphics.SurfaceTexture.OnFrameAvailableListener;
import android.os.Handler;
import android.os.Process;
import android.util.AttributeSet;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.opengl.EGLContext;
import android.opengl.GLES20;

import android.util.Log;
import android.opengl.EGL14;

import java.io.Writer;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.HashSet;
import java.util.Set;

import java.util.ArrayList;
import java.util.concurrent.Semaphore;

//import vdroid.api.internal.platform.media.FvlSurfaceManager;
//import vdroid.api.internal.platform.media.FvlTextureManager;
import com.fanvil.videoconference.FvlLogger;

public class FvlVideoConsumer extends FvlVideoConsumerBase implements FvlVideoConsumerBase.Renderer,
        FvlVideoConsumerBase.EGLFactory,
        FvlVideoConsumerBase.GLFactory,
        SurfaceTexture.OnFrameAvailableListener {
    private static FvlLogger sLogger = FvlLogger.getLogger(FvlVideoConsumer.class.getSimpleName(), FvlLogger.DEBUG);

    //private final static boolean LOG_ATTACH_DETACH = true;
    //private final static boolean LOG_THREADS = true;
    //private final static boolean LOG_STOP_START = true;
    private final static boolean LOG_OUTPUT_SURFACE = true;
    private final static boolean LOG_SHARE_SURFACE = true;
    //private final static boolean LOG_RENDERER = true;
    //private final static boolean LOG_RENDERER_DRAW_FRAME = true;
    //private final static boolean LOG_EGL = true;
    //private final static boolean LOG_GL = true;

    public static final int ID_LOCAL = 0;
    public static final int ID_REMOTE1 = 1;
    public static final int ID_REMOTE2 = 2;
    public static final int ID_REMOTE3 = 3;
    public static final int ID_REMOTE4 = 4;
    public static final int ID_OUTPUT_MAX = 5;
    public static final int ID_INPUT_MAX = 3;

    public static final int ID_SHARE1 = 0;
    public static final int ID_SHARE2 = 1;
    public static final int ID_SHARE_MAX = 2;


    private String mTag = null;
    private String mName = null;

    //UI Surface 对应关系
    private ConcurrentHashMap<Integer, Surface> mOutputSurfaces
            = new ConcurrentHashMap<Integer, Surface>();

    //需要送出去的 Surface对应关系 应该是用于混合视频
    private ConcurrentHashMap<Integer, Surface> mShareSurfaces
            = new ConcurrentHashMap<Integer, Surface>();

    private ConcurrentHashMap<Integer, SurfaceTexture> mInputSurfaceTextures
            = new ConcurrentHashMap<Integer, SurfaceTexture>();

    private ConcurrentHashMap<Integer, Surface> mInputSurfaces
            = new ConcurrentHashMap<Integer, Surface>();

    /** Map of render targets (one for each Surface) */
    private ConcurrentHashMap<Surface, FvlRenderTarget> mOutputTargets
            = new ConcurrentHashMap<Surface, FvlRenderTarget>();

    private ConcurrentHashMap<Surface, FvlRenderTarget> mShareTargets
            = new ConcurrentHashMap<Surface, FvlRenderTarget>();

    private ConcurrentHashMap<SurfaceTexture, FvlRenderTarget> mTextureTargets
            = new ConcurrentHashMap<SurfaceTexture, FvlRenderTarget>();

    private ConcurrentHashMap<SurfaceTexture, FvlTextureSource> mTextureSources
        = new ConcurrentHashMap<SurfaceTexture, FvlTextureSource>();

    private ConcurrentHashMap<SurfaceTexture, FvlTextureRenderer> mTextureRenderers
        = new ConcurrentHashMap<SurfaceTexture, FvlTextureRenderer>();

    /** Map of set of Output Surfaces (one for each SurfaceTexture) */
    private ConcurrentHashMap<SurfaceTexture, ConcurrentHashMap<Integer, Surface>> mTextureOutputSurfaces
            = new ConcurrentHashMap<SurfaceTexture, ConcurrentHashMap<Integer, Surface>>();

    private ConcurrentHashMap<Surface, ConcurrentHashMap<Integer, SurfaceTexture>> mSurfaceInputTextures
            = new ConcurrentHashMap<Surface, ConcurrentHashMap<Integer, SurfaceTexture>>();

    private FvlRenderTarget mRenderTarget = null;
    private FvlTextureSource mTextureSource = null;
    private FvlTextureRenderer mTextureRender = null;

    private long ts = 0;
    private long oldts = 0;

    private static FvlVideoConsumer sInstance;
    private static FvlVideoConsumer sLocalInstance;
    private static FvlVideoConsumer sRemoteInstance;

    public static FvlVideoConsumer getInstance() {
        if (sInstance == null) {
            sInstance = new FvlVideoConsumer();
        }
        return sInstance;
    }
    //未使用
    public static FvlVideoConsumer getLocal() {
        if (sLocalInstance == null) {
            sLocalInstance = new FvlVideoConsumer("localdisplay");
        }
        return sLocalInstance;
    }
    //未使用
    public static FvlVideoConsumer getRemote() {
        if (sRemoteInstance == null) {
            sRemoteInstance = new FvlVideoConsumer("remotedisplay");
        }
        return sRemoteInstance;
    }

    public static FvlVideoConsumer create(String name) {
        return new FvlVideoConsumer(name);
    }

    private FvlVideoConsumer() {
        super();
    }

    private FvlVideoConsumer(String name) {
        super(name);
        mTag = name;
        mName = name;
        setEGLFactory(this);
        setGLFactory(this);
        setRenderer(this);
        init();
        if (sLogger.isLoggable()) sLogger.d("[" + mName + "] Created");
    }

    public void setVideoSize(int index, int videoW, int videoH) {
        if (sLogger.isLoggable()) sLogger.d("[" + mName + "] setVideoSize: " + videoW + "X" + videoH);
        //if (mGLThread != null) {
        //    mGLThread.setVideoSize(videoW, videoH);
        //}
    }

    public void setViewSize(int index, int viewW, int viewH) {
        if (sLogger.isLoggable()) sLogger.d("[" + mName + "] setViewSize: " + viewW + "X" + viewH);
        //if (mGLThread != null) {
        //    mGLThread.setViewSize(viewW, viewH);
        //}
    }

    public void setViewRole(int index, int role) {
        if (sLogger.isLoggable()) sLogger.d("[" + mName + "] setViewRole: " + role);
       // if (mGLThread != null) {
       //     mGLThread.setViewRole(role);
       // }
    }

    public void addOutputSurface(int index, Surface surface) {
        if (LOG_OUTPUT_SURFACE) {
            sLogger.d("[" + mName + "] addOutputSurface " + index + " begin");
        }

        mOutputSurfaces.put(index, surface);
        surfaceCreated();

        if (LOG_OUTPUT_SURFACE) {
            for (Integer id : mOutputSurfaces.keySet()) {
                Surface sf = mOutputSurfaces.get(id);
                sLogger.d("[" + mName + "] addOutputSurface: show Id:"+ id + " Surface:" + sf);
            }
        }
        if (LOG_OUTPUT_SURFACE) {
            sLogger.d("[" + mName + "] addOutputSurface " + index + " end");
        }
    }

    public void removeOutputSurface(int index) {
        if (LOG_OUTPUT_SURFACE) {
            sLogger.d("[" + mName + "] removeOutputSurface " + index + " begin");
        }

        surfaceDestroyed();
        mOutputSurfaces.remove(index);

        if (LOG_OUTPUT_SURFACE) {
            for (Integer id : mOutputSurfaces.keySet()) {
                Surface sf = mOutputSurfaces.get(id);
                sLogger.d("[" + mName + "] removeOutputSurface: show Id:"+ id + " Surface:" + sf);
            }
            sLogger.d("[" + mName + "] removeOutputSurface " + index + " end");
        }
    }

    public int getAvailableRemoteId() {
        for (Integer id : mOutputSurfaces.keySet()) {
            Surface sf = mOutputSurfaces.get(id);
            if (id > 0 && sf == null) {
                return id;
            }
        }
        return -1;
    }

    public void addShareSurface(int index, Surface surface) {
        if (LOG_SHARE_SURFACE) {
            sLogger.d("[" + mName + "] addShareSurface ID:" + index + " Share Surface:" + surface + " begin");
        }
        mShareSurfaces.put(index, surface);
        surfaceCreated();

        if (LOG_SHARE_SURFACE) {
            for (Integer id : mShareSurfaces.keySet()) {
                Surface sf = mShareSurfaces.get(id);
                sLogger.d("[" + mName + "] addShareSurface: show "+ id + " Share Surface: " + sf);
            }
            sLogger.d("[" + mName + "] addShareSurface " + index + " end");
        }
    }

    public void removeShareSurface(int index) {
        if (sLogger.isLoggable()) sLogger.d("[" + mName + "] removeShareSurface " + index + " begin");
        surfaceDestroyed();
        mShareSurfaces.remove(index);
        for (Integer id : mShareSurfaces.keySet()) {
            Surface sf = mShareSurfaces.get(id);
            sLogger.d("[" + mName + "] removeShareSurface: show "+ id + " Share Surface: " + sf);
        }
        if (sLogger.isLoggable()) sLogger.d("[" + mName + "] removeShareSurface " + index + " end");
    }

    public int getAvailableShareId() {
        for (Integer id : mShareSurfaces.keySet()) {
            Surface sf = mShareSurfaces.get(id);
            if (id > 0 && sf == null) {
                return id;
            }
        }
        return -1;
    }

    public SurfaceTexture getSurfaceTexture(int index) {
        if (sLogger.isLoggable()) sLogger.d("[" + mName + "] getSurfaceTexture index=" + index);
        SurfaceTexture surfaceTexture = mInputSurfaceTextures.get(Integer.valueOf(index));
        if (surfaceTexture == null) {
            if (sLogger.isLoggable()) sLogger.d("[" + mName + "] getSurfaceTexture faield, index=" + index);
        }
        return surfaceTexture;
    }

    public Surface getSurface(int index) {
        if (sLogger.isLoggable()) sLogger.d("[" + mName + "] getSurface index=" + index);
        Surface surface = mInputSurfaces.get(index);
        if (surface != null) {
            if (sLogger.isLoggable()) sLogger.d("[" + mName + "] getSurface already created index=" + index);
            return surface;
        }
        SurfaceTexture surfaceTexture = mInputSurfaceTextures.get(Integer.valueOf(index));
        if (surfaceTexture != null) {
            surface = new Surface(surfaceTexture);
            mInputSurfaces.put(index, surface);
            if (sLogger.isLoggable()) sLogger.d("[" + mName + "] getSurface create new Surface index=" + index);
            return surface;
        }
        if (sLogger.isLoggable()) sLogger.d("[" + mName + "] getSurface faield index=" + index);
        return null;
    }

    public void start() {
        super.checkAndStart();
    }

    public void stop() {
        super.stop();
    }

    public void release() {
        super.release();
    }

    public boolean createRenderTarget() {
        for (Integer index : mOutputSurfaces.keySet()) {
            if (FvlRenderTarget.currentTarget() == null) {
                getRenderTarget().focus();
            }
            Surface surface = mOutputSurfaces.get(index);
            if (surface != null) {
                renderTargetFor(surface);
            }
        }

        for (Integer index : mShareSurfaces.keySet()) {
            if (FvlRenderTarget.currentTarget() == null) {
                getRenderTarget().focus();
            }
            Surface surface = mShareSurfaces.get(index);
            if (surface != null) {
                renderTargetForShare(surface);
            }
        }

        updateOutputSurface();
        updateShareSurface();

        for (Integer id : mOutputSurfaces.keySet()) {
            Surface surface = mOutputSurfaces.get(id);
            sLogger.d("[" + mName + "] createRenderTarget: show " + id + " Output Surface: " + surface);
        }
        for (Integer id : mShareSurfaces.keySet()) {
            Surface surface = mShareSurfaces.get(id);
            sLogger.d("[" + mName + "] createRenderTarget: show " + id + " Share Surface: " + surface);
        }
        return true;
    }

    public boolean createTextureSource() {
        sLogger.d("[" + mName + "] createTextureSource");

        boolean result = false;
        
        FvlRenderTarget.showCurrent();
        FvlRenderTarget renderTarget = getRenderTarget();
        if (FvlRenderTarget.currentTarget() == null) {
            renderTarget.focus();
        }

        int i = 0;
        for (i = 0; i < 3; i++) {
            SurfaceTexture surfaceTexture = mInputSurfaceTextures.get(Integer.valueOf(i));
            if (surfaceTexture == null) {
                FvlRenderTarget.showCurrent();
                FvlTextureRenderer renderer = new FvlTextureRenderer();
                FvlTextureSource textureSource = FvlTextureSource.newExternalTexture();
                renderer.bindVbo(textureSource.getVboId());
                textureSource.bind();
                textureSource.setParameterf(GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
                textureSource.setParameterf(GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
                textureSource.setParameteri(GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
                textureSource.setParameteri(GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

                surfaceTexture = new SurfaceTexture(textureSource.getTextureId());
                surfaceTexture.setDefaultBufferSize(1920, 1080);
                surfaceTexture.setOnFrameAvailableListener(this);
                surfaceTexture.detachFromGLContext();

                mInputSurfaceTextures.put(Integer.valueOf(i), surfaceTexture);
                sLogger.d("[" + mName + "] createTextureSource: add "+ Integer.valueOf(i) +" input SurfaceTexture" + surfaceTexture);
                mTextureSources.put(surfaceTexture, textureSource);
                mTextureTargets.put(surfaceTexture, renderTarget);
                mTextureRenderers.put(surfaceTexture, renderer);

                updateOutputSurface();
                updateShareSurface();

                result = true;
            } else {
                sLogger.d("[" + mName + "] createTextureSource: already added "+ Integer.valueOf(i) +" input SurfaceTexture" + surfaceTexture);
            }
        }
        for (Integer id : mInputSurfaceTextures.keySet()) {
            SurfaceTexture st = mInputSurfaceTextures.get(id);
            sLogger.d("[" + mName + "] createTextureSource: show "+ id + " SurfaceTexture: " + st);
        }
        for (Integer id : mInputSurfaces.keySet()) {
            Surface sf = mInputSurfaces.get(id);
            sLogger.d("[" + mName + "] createTextureSource: show " + id + " Input Surface: " + sf);
        }
        for (FvlTextureSource source : mTextureSources.values()) {
            sLogger.d("[" + mName + "] createTextureSource: show FvlTextureSource: " + source);
        }
        for (FvlRenderTarget target : mTextureTargets.values()) {
            sLogger.d("[" + mName + "] createTextureSource: show FvlRenderTarget: " + target);
        }
        for (FvlTextureRenderer renderer : mTextureRenderers.values()) {
            sLogger.d("[" + mName + "] createTextureSource: show FvlTextureRenderer: " + renderer);
        }

        return result;
    }

    public boolean createTextureRenderer() {
        sLogger.d("[" + mName + "] createTextureRenderer");
        /*if (mTextureRender == null) {
            mTextureRender = new FvlTextureRenderer();
        }*/
        return true;
    }

    public boolean onDrawFrame() {
        sLogger.d("[" + mName + "] onDrawFrame begin");

        sLogger.d("[" + mName + "] onDrawFrame: out surface --------------------------------------");
        boolean update = false;
        for (Integer index : mOutputSurfaces.keySet()) {
            Surface surface = mOutputSurfaces.get(index);
            sLogger.d("[" + mName + "] onDrawFrame: index " + index +" output Surface: (" + surface);
            if (surface != null) {
                FvlRenderTarget surfaceTarget = mOutputTargets.get(surface);
                final ConcurrentHashMap<Integer, SurfaceTexture> inputSurfaceTextures = texturesForSurface(surface);
                if (surfaceTarget != null && inputSurfaceTextures != null) {
                    //if (surfaceTarget != null) {
                        surfaceTarget.focus();
                        for (Integer id : inputSurfaceTextures.keySet()) {
                            SurfaceTexture surfaceTexture = inputSurfaceTextures.get(id);
                            FvlTextureRenderer renderer = mTextureRenderers.get(surfaceTexture);
                            FvlTextureSource source = mTextureSources.get(surfaceTexture);

                            if (surfaceTexture != null && isAvailable(surfaceTexture) && renderer != null && source != null) {
                                removeReferenceTo(surfaceTexture);

                                sLogger.d("[" + mName + "] onDrawFrame: index "+ id +" output surface: " + surface + " renderTarget=" + surfaceTarget);

                                surfaceTexture.attachToGLContext(source.getTextureId());
                                sLogger.d("[" + mName + "] onDrawFrame: input SurfaceTexture: (" + surfaceTexture + ") attach to "+mTextureSources.get(surfaceTexture).getTextureId());
                                sLogger.d("[" + mName + "] onDrawFrame: input SurfaceTexture: (" + surfaceTexture + ")  "+ id +" output surface: " + surface+" updateTexImage");
                                    
                                surfaceTexture.updateTexImage();
                                update = true;
                                oldts = ts;
                                ts = surfaceTexture.getTimestamp();
                                sLogger.d("[" + mName + "] onDrawFrame: input SurfaceTexture: (" + surfaceTexture + ")  "+ id +" output surface: " + surface+" TS: "+ts);
                                if (oldts != 0) {
                                    sLogger.d("[" + mName + "] onDrawFrame: input SurfaceTexture: (" + surfaceTexture + ")  "+ id +" output surface: " + surface+" FPS: "+(1000000000/(ts-oldts)));
                                }
                                renderer.process(3, surfaceTexture, source, surfaceTarget, surfaceTarget.getWidth(), surfaceTarget.getHeight());
                                surfaceTexture.detachFromGLContext();
                                sLogger.d("[" + mName + "] onDrawFrame: input SurfaceTexture: (" + surfaceTexture + ") detachFromGLContext");

                            } else {
                                sLogger.d("[" + mName + "] onDrawFrame: index "+ id +" output surface: " + surface + " renderTarget=" + surfaceTarget);
                                sLogger.e("[" + mName + "] onDrawFrame: input SurfaceTexture: " + surfaceTexture + " failed");
                                if (surfaceTexture == null) {
                                    sLogger.e("[" + mName + "] onDrawFrame: input SurfaceTexture: SurfaceTexture is null");
                                }
                                if (renderer == null) {
                                    sLogger.e("[" + mName + "] onDrawFrame: input SurfaceTexture: FvlTextureRenderer is null");
                                }
                                if (source == null) {
                                    sLogger.e("[" + mName + "] onDrawFrame: input SurfaceTexture: FvlTextureSource is null");
                                }
                            }
                        }
                        surfaceTarget.swapBuffers();
                    //} else {
                    //    sLogger.d("[" + mName + "] onDrawFrame: output Surface: " + surface + " DO NOT have render target");
                    //}
                } else {
                    if (surfaceTarget == null) {
                        sLogger.e("[" + mName + "] onDrawFrame: input SurfaceTexture: FvlRenderTarget is null");
                    }
                    if (inputSurfaceTextures == null) {
                        sLogger.e("[" + mName + "] onDrawFrame: input SurfaceTexture: ConcurrentHashMap<Integer, SurfaceTexture> is null");
                    }
                }
            } else {
                sLogger.e("[" + mName + "] onDrawFrame: input SurfaceTexture: surface is null");
            }
        }

        sLogger.d("[" + mName + "] onDrawFrame: output surface +++++++++++++++++++++++++++++++++++++");
        sLogger.d("[" + mName + "] onDrawFrame: share surface --------------------------------------");
        int updateID = -1;
        for (Integer index : mShareSurfaces.keySet()) {
            Surface surface = mShareSurfaces.get(index);
            sLogger.d("[" + mName + "] onDrawFrame: index " + index +" share Surface: (" + surface);
            if (surface != null) {
                FvlRenderTarget surfaceTarget = mShareTargets.get(surface);
                final ConcurrentHashMap<Integer, SurfaceTexture> inputSurfaceTextures = texturesForSurface(surface);
                if (surfaceTarget != null && inputSurfaceTextures != null) {
                    int mode = -1;
                    //if (surfaceTarget != null) {
                        surfaceTarget.focus();
                        for (Integer id : inputSurfaceTextures.keySet()) {
                            SurfaceTexture surfaceTexture = inputSurfaceTextures.get(id);
                            FvlTextureRenderer renderer = mTextureRenderers.get(surfaceTexture);
                            FvlTextureSource source = mTextureSources.get(surfaceTexture);

                            if (surfaceTexture != null && update/*isAvailable(surfaceTexture) */&& renderer != null && source != null) {

                                sLogger.d("[" + mName + "] onDrawFrame: index "+ id +" share surface: " + surface + " share renderTarget=" + surfaceTarget);

                                surfaceTexture.attachToGLContext(mTextureSources.get(surfaceTexture).getTextureId());
                                sLogger.d("[" + mName + "] onDrawFrame: input SurfaceTexture: (" + surfaceTexture + ") share attach to "+mTextureSources.get(surfaceTexture).getTextureId());
                                oldts = ts;
                                ts = surfaceTexture.getTimestamp();
                                sLogger.d("[" + mName + "] onDrawFrame: input SurfaceTexture: (" + surfaceTexture + ")  "+ id +" share surface: " + surface+" TS: "+ts);
                                if (oldts != 0) {
                                    sLogger.d("[" + mName + "] onDrawFrame: input SurfaceTexture: (" + surfaceTexture + ")  "+ id +" share surface: " + surface+" FPS: "+(1000000000/(ts-oldts)));
                                }
                                if (id == ID_LOCAL) {
                                    updateID = ID_LOCAL;
                                }
                                if (id == ID_LOCAL && (index == ID_SHARE1 || index == ID_SHARE2)) {
                                    mode = 1;
                                } else if (id == ID_REMOTE1 && (index == ID_SHARE1 || index == ID_SHARE2)) {
                                    mode = 0;
                                } else if (id == ID_REMOTE2 && (index == ID_SHARE1 || index == ID_SHARE2)) {
                                    mode = 2;
                                } else {
                                    mode = 3;
                                }
                                renderer.process(mode, surfaceTexture, source, surfaceTarget, surfaceTarget.getWidth(), surfaceTarget.getHeight());
                                surfaceTexture.detachFromGLContext();
                                sLogger.d("[" + mName + "] onDrawFrame: input SurfaceTexture: (" + surfaceTexture + ") share detachFromGLContext");

                            } else {
                                sLogger.d("[" + mName + "] onDrawFrame: index "+ id +" share surface: " + surface + " renderTarget=" + surfaceTarget);
                                sLogger.e("[" + mName + "] onDrawFrame: share input SurfaceTexture: " + surfaceTexture + " failed");
                                if (surfaceTexture == null) {
                                    sLogger.e("[" + mName + "] onDrawFrame: share input SurfaceTexture: SurfaceTexture is null");
                                }
                                if (renderer == null) {
                                    sLogger.e("[" + mName + "] onDrawFrame: share input SurfaceTexture: FvlTextureRenderer is null");
                                }
                                if (source == null) {
                                    sLogger.e("[" + mName + "] onDrawFrame: share input SurfaceTexture: FvlTextureSource is null");
                                }
                            }
                        }
                        if (updateID == ID_LOCAL) {
                            surfaceTarget.swapBuffers();
                        }
                    //} else {
                    //    sLogger.d("[" + mName + "] onDrawFrame: share Surface: " + surface + " DO NOT have render target");
                    //}
                } else {
                    if (surfaceTarget == null) {
                        sLogger.e("[" + mName + "] onDrawFrame: share input SurfaceTexture: FvlRenderTarget is null");
                    }
                    if (inputSurfaceTextures == null) {
                        sLogger.e("[" + mName + "] onDrawFrame: share input SurfaceTexture: ConcurrentHashMap<Integer, SurfaceTexture> is null");
                    }
                }
            } else {
                    sLogger.e("[" + mName + "] onDrawFrame: share input SurfaceTexture: surface is null");
            }
        }
        
        sLogger.d("[" + mName + "] onDrawFrame: share surface +++++++++++++++++++++++++++++++++++++");
        sLogger.d("[" + mName + "] onDrawFrame end");
        return true;

    }

    /** A Map for performing reference counting over shared objects across RenderTargets */
    private static ConcurrentHashMap<Object, Integer> mRefCounts = new ConcurrentHashMap<Object, Integer>();

    private void addReferenceTo(Object object) {
        Integer refCount = mRefCounts.get(object);
        if (refCount != null) {
            mRefCounts.put(object, refCount + 1);
        } else {
            mRefCounts.put(object, 1);
        }
        
        sLogger.d("addReferenceTo: SurfaceTexture="+ object + " Ref=" + mRefCounts.get(object));
    }

    private boolean removeReferenceTo(Object object) {
        Integer refCount = mRefCounts.get(object);
        if (refCount != null && refCount > 0) {
            --refCount;
            mRefCounts.put(object, refCount);
            sLogger.d("removeReferenceTo: SurfaceTexture="+ object + " Ref=" + mRefCounts.get(object));
            return refCount == 0;
        } else {
            sLogger.e("[" + mName + "] Removing reference of already released: " + object + "!");
            return false;
        }
    }

    private boolean isAvailable(Object object) {
        Integer refCount = mRefCounts.get(object);
        if (refCount != null && refCount > 0) {
            sLogger.d("isAvailable: TRUE SurfaceTexture="+ object + " Ref=" + mRefCounts.get(object));
            return true;
        }
        sLogger.d("isAvailable: FALSE SurfaceTexture="+ object + " Ref=" + mRefCounts.get(object));
        return false;
    }

    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        sLogger.d("[" + mName + "] onFrameAvailable:" + surfaceTexture);
        addReferenceTo(surfaceTexture);
        super.requestRender();
    }
    
    private ConcurrentHashMap<Integer, Surface> surfacesForSurfaceTexture(SurfaceTexture surfaceTex) {
        sLogger.d("[" + mName + "] surfacesForSurfaceTexture");
        ConcurrentHashMap<Integer, Surface> surfaces = mTextureOutputSurfaces.get(surfaceTex);
        if (surfaces == null) {
            surfaces = new ConcurrentHashMap<Integer, Surface>();
            mTextureOutputSurfaces.put(surfaceTex, surfaces);
        }
        return surfaces;
    }
    
    private ConcurrentHashMap<Integer, SurfaceTexture> texturesForSurface(Surface surface) {
        sLogger.d("[" + mName + "] texturesForSurface");
        ConcurrentHashMap<Integer, SurfaceTexture> textures = mSurfaceInputTextures.get(surface);
        if (textures == null) {
            textures = new ConcurrentHashMap<Integer, SurfaceTexture>();
            mSurfaceInputTextures.put(surface, textures);
        }
        return textures;
    }

    private void addSurfaceTextureForSurface(Surface surface, int index) {
        SurfaceTexture surfaceTex = mInputSurfaceTextures.get(Integer.valueOf(index));
        if (surface != null && surfaceTex != null) {
            ConcurrentHashMap<Integer, SurfaceTexture> textures = texturesForSurface(surface);
            if (textures != null && !textures.containsValue(surfaceTex)) {
                textures.put(index, surfaceTex);
                sLogger.d("[" + mName + "] addSurfaceTextureForSurface: add index=" + index + " SurfaceTexture=" + surfaceTex);
            }
        } else {
            sLogger.d("[" + mName + "] addSurfaceTextureForSurface: "+ index + " SurfaceTexture is null");
        }
    }

    private FvlRenderTarget getRenderTarget() {
        if (mRenderTarget == null) {
            mRenderTarget = FvlRenderTarget.newTarget(1, 1);
        }
        return mRenderTarget;
    }

    private FvlRenderTarget renderTargetFor(Surface surface) {
        FvlRenderTarget target = mOutputTargets.get(surface);
        if (target == null) {
            target = getRenderTarget().forSurface(surface);
            sLogger.d("[" + mName + "] renderTargetFor: add :" + surface + " RenderTarget= " + target);
            mOutputTargets.put(surface, target);
        }
        sLogger.d("[" + mName + "] renderTargetFor: show ---------------------------------------");
        for (Surface sf : mOutputTargets.keySet()) {
            sLogger.d("[" + mName + "] renderTargetFor: show :" + sf + " RenderTarget= " + mOutputTargets.get(sf));
        }
        sLogger.d("[" + mName + "] renderTargetFor: show +++++++++++++++++++++++++++++++++++++++");
        return target;
    }

    private FvlRenderTarget renderTargetForShare(Surface surface) {
        FvlRenderTarget target = mShareTargets.get(surface);
        if (target == null) {
            target = FvlRenderTarget.newTarget(surface, getRenderTarget().getContext());
            sLogger.d("[" + mName + "] renderTargetForShare: add :" + surface + " RenderTarget= " + target);
            mShareTargets.put(surface, target);
        }
        sLogger.d("[" + mName + "] renderTargetForShare: show ---------------------------------------");
        for (Surface sf : mShareTargets.keySet()) {
            sLogger.d("[" + mName + "] renderTargetForShare: show :" + sf + " RenderTarget= " + mShareTargets.get(sf));
        }
        sLogger.d("[" + mName + "] renderTargetForShare: show +++++++++++++++++++++++++++++++++++++++");
        return target;
    }

    private void updateOutputSurface() {
        sLogger.d("[" + mName + "] updateOutputSurface");

        for (Integer index : mOutputSurfaces.keySet()) {
            Surface surface = mOutputSurfaces.get(index);
            if (surface != null) {
                sLogger.d("[" + mName + "] updateOutputSurface: index=" + index + " Surface=" + surface);
                addSurfaceTextureForSurface(surface, index.intValue());
            }
        }
    }

    private void updateShareSurface() {
        sLogger.d("[" + mName + "] updateShareSurface");

        for (Integer index : mShareSurfaces.keySet()) {
            Surface surface = mShareSurfaces.get(index);
            if (surface != null) {
                sLogger.d("[" + mName + "] updateShareSurface: index=" + index + " Surface=" + surface);
                addSurfaceTextureForSurface(surface, ID_LOCAL);
                addSurfaceTextureForSurface(surface, ID_REMOTE1);
                addSurfaceTextureForSurface(surface, ID_REMOTE2);
            }
        }
    }
}
